#include "ObjLoader.h"
#include <fstream>
#include <vector>
using namespace std;

#define MAX_PATH 255

//--------------------------------------------------------------------------------------
CObjLoader::CObjLoader()
{
    m_NumAttribTableEntries = 0;
}


//--------------------------------------------------------------------------------------
CObjLoader::~CObjLoader()
{
    Destroy();
}


//--------------------------------------------------------------------------------------
void CObjLoader::Destroy()
{
    for ( mUInt iMaterial = 0; iMaterial < m_Materials.GetSize(); ++iMaterial )
    {
        TObj_Material *pMaterial = m_Materials.GetAt( iMaterial );

        //if ( pMaterial->pTextureRV10 && !IsErrorResource(pMaterial->pTextureRV10) )
        //{
        //    ID3D10Resource* pRes = NULL;
        //    
        //    pMaterial->pTextureRV10->GetResource( &pRes );
        //    SAFE_RELEASE( pRes );
        //    SAFE_RELEASE( pRes );   // do this twice, because GetResource adds a ref

        //    SAFE_RELEASE( pMaterial->pTextureRV10 );
        //}

        SAFE_DELETE( pMaterial );
    }

    m_Materials.RemoveAll();
    m_Vertices.RemoveAll();
    m_Indices.RemoveAll();
    m_Attributes.RemoveAll();
    m_NumAttribTableEntries = 0;
}


//--------------------------------------------------------------------------------------
mBool CObjLoader::Create( mString strFilename )
{
    // Start clean
    Destroy();

    

    // Load the vertex buffer, index buffer, and subset information from a file. In this case, 
    // an .obj file was chosen for simplicity, but it's meant to illustrate that ID3DXMesh objects
    // can be filled from any mesh file format once the necessary data is extracted from file.
    LoadGeometryFromOBJ( strFilename );


    /* Load material textures
    for ( mUInt iMaterial = 0; iMaterial < m_Materials.GetSize(); ++iMaterial )
    {
        TObj_Material *pMaterial = m_Materials.GetAt( iMaterial );
        if ( pMaterial->strTexture[0] )
        {            
            //load texture
        }
    }*/

    /*D3D10_BUFFER_DESC BufferDesc = 
    {
        m_Vertices.GetSize() * sizeof(TObj_Vertex),
        D3D10_USAGE_DEFAULT,
        D3D10_BIND_VERTEX_BUFFER,
        0, 0
    };
    D3D10_SUBRESOURCE_DATA BufferInitData;
    BufferInitData.pSysMem = m_Vertices.GetData();
    BufferInitData.SysMemPitch = 0;
    BufferInitData.SysMemSlicePitch = 0;
    pd3dDevice->CreateBuffer(&BufferDesc, &BufferInitData, &m_pVbuf);
    BufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
    BufferDesc.ByteWidth = m_Indices.GetSize() * sizeof(mUInt);
    BufferInitData.pSysMem = m_Indices.GetData();
    pd3dDevice->CreateBuffer(&BufferDesc, &BufferInitData, &m_pIbuf);*/

    return true;
}


//--------------------------------------------------------------------------------------
mBool CObjLoader::LoadGeometryFromOBJ( mString strFileName )
{
    // Create temporary storage for the input data. Once the data has been loaded into
    // a reasonable format we can create a D3DXMesh object and load it with the mesh data.
    CDynamicArray <float> Positions;
    CDynamicArray <float> TexCoords;
    CDynamicArray <float> Normals;

    TObj_Material* pMaterial = new TObj_Material();
    //InitMaterial( pMaterial );
    pMaterial->strName = "default";
    m_Materials.Add( pMaterial );

    mUInt dwCurSubset = 0;

    // File input
    char strCommand[256] = {0};
    char strMaterialFilename[256] = {0};
    std::vector<TObj_Vertex> FaceVertices;
    std::vector<mUInt>   FaceVerticesInd;
    std::vector<mUInt>   FaceIndices;
    ifstream InFile( strFileName.c_str() );

    for(; ; )
    {
        InFile >> strCommand;
        if( !InFile )
            break;

        if( 0 == /*wcscmp*/strcmp( strCommand, "#" ) )
        {
            // Comment
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "v" ) )
        {
            // Vertex Position
            float x, y, z;
            InFile >> x >> y >> z;
            //Positions.Add( D3DXVECTOR3( x, y, z ) );
            Positions.Add( x );
            Positions.Add( y );
            Positions.Add( z );
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "vt" ) )
        {
            // Vertex TexCoord
            float u, v;
            InFile >> u;//
            InFile >> v;
            //TexCoords.Add( D3DXVECTOR2( u, v ) );
            TexCoords.Add( u );
            TexCoords.Add( v );
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "vn" ) )
        {
            // Vertex Normal
            float x, y, z;
            InFile >> x >> y >> z;
            //Normals.Add( D3DXVECTOR3( x, y, z ) );
            Normals.Add( x );
            Normals.Add( y );
            Normals.Add( z );
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "f" ) )
        {
            // Face
            mUInt iPosition, iTexCoord, iNormal;
            TObj_Vertex vertex;
            
            wchar_t symb;
            FaceVertices.clear();
            FaceVerticesInd.clear();

            //for( mUInt iFace = 0; iFace < 3; iFace++ )
            for (;;)
            {
                //ZeroMemory( &vertex, sizeof( TObj_Vertex ) );

                //skipping spaces
                while (InFile.peek() == ' ')
                    InFile.ignore();

                symb = InFile.peek();
                if ( symb == '\n' )
                {
                    //InFile.ignore();
                    break;
                }
                // OBJ format uses 1-based arrays
                InFile >> iPosition;
                //vertex.position = Positions[ iPosition - 1 ];
                vertex.position[0] = Positions[ 3 * (iPosition - 1) ];
                vertex.position[1] = Positions[ 3 * (iPosition - 1) + 1 ];
                vertex.position[2] = Positions[ 3 * (iPosition - 1) + 2 ];

                if( '/' == InFile.peek() )
                {
                    InFile.ignore();

                    if( '/' != InFile.peek() )
                    {
                        // Optional texture coordinate
                        InFile >> iTexCoord;
                        //vertex.texcoord = TexCoords[ iTexCoord - 1 ];
                        vertex.texcoord[0] = TexCoords[ 2*(iTexCoord - 1) ];
                        vertex.texcoord[1] = TexCoords[ 2*(iTexCoord - 1) + 1 ];
                    }

                    if( '/' == InFile.peek() )
                    {
                        InFile.ignore();

                        // Optional vertex normal
                        InFile >> iNormal;
                        //vertex.normal = Normals[ iNormal - 1 ];
                        vertex.normal[0] = Normals[ 3 * (iNormal - 1) ];
                        vertex.normal[1] = Normals[ 3 * (iNormal - 1) + 1 ];
                        vertex.normal[2] = Normals[ 3 * (iNormal - 1) + 2 ];
                    }
                }
                FaceVertices.push_back(vertex);
                FaceVerticesInd.push_back(iPosition);

                // If a duplicate vertex doesn't exist, add this vertex to the Vertices
                // list. Store the index in the Indices array. The Vertices and Indices
                // lists will eventually become the Vertex Buffer and Index Buffer for
                // the mesh.
                /*mUInt index = AddVertex( iPosition, &vertex );
                if ( index == (mUInt)-1 )
                   return false;

                m_Indices.Add( index );*/
            }
            FaceIndices.resize(FaceVertices.size());
            for (mUInt i = 0; i < FaceVertices.size(); i++ )
            {
                //triangle strip
                if (i > 2)
                {                    
                    m_Indices.Add( FaceIndices[i-3] );
                    m_Indices.Add( FaceIndices[i-1] );
                }
                FaceIndices[i] = AddVertex( FaceVerticesInd[i], &FaceVertices[i] );
                if ( FaceIndices[i] == (mUInt)-1 )
                    return false;

                m_Indices.Add( FaceIndices[i] );
            }
            
            m_Attributes.Add( dwCurSubset );
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "mtllib" ) )
        {
            // TObj_Material library
            InFile >> strMaterialFilename;
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "usemtl" ) )
        {
            // TObj_Material
            char strName[250] = {0};
            InFile >> strName;

            mBool bFound = false;
            for( mUInt iMaterial = 0; iMaterial < m_Materials.GetSize(); iMaterial++ )
            {
                TObj_Material* pCurMaterial = m_Materials.GetAt( iMaterial );
                if( 0 == /*wcscmp*/strcmp( pCurMaterial->strName.c_str(), strName ) )
                {
                    bFound = true;
                    dwCurSubset = iMaterial;
                    break;
                }
            }

            if( !bFound )
            {
                pMaterial = new TObj_Material();
                if( pMaterial == NULL )
                    return false;

                dwCurSubset = m_Materials.GetSize();

                //InitMaterial( pMaterial );
                pMaterial->strName = strName;

                m_Materials.Add( pMaterial );
            }
        }
        else
        {
            // Unimplemented or unrecognized command
        }

        InFile.ignore( 1000, '\n' );
    }

    /* Position analysis: getting AABB */
    //mUInt i;
    //D3DXVECTOR3 vBBoxMin;
    //D3DXVECTOR3 vBBoxMax;
    //D3DXVECTOR3 *pCurrent;
    //vBBoxMax = vBBoxMin = Positions.GetAt(0);
    //for (i = 1; i < Positions.GetSize(); i++)
    //{
    //    pCurrent = &Positions.GetAt(i);
    //    //min
    //    if (vBBoxMin.x > pCurrent->x)
    //        vBBoxMin.x = pCurrent->x;
    //    if (vBBoxMin.y > pCurrent->y)
    //        vBBoxMin.y = pCurrent->y;
    //    if (vBBoxMin.z > pCurrent->z)
    //        vBBoxMin.z = pCurrent->z;
    //    //max
    //    if (vBBoxMax.x < pCurrent->x)
    //        vBBoxMax.x = pCurrent->x;
    //    if (vBBoxMax.y < pCurrent->y)
    //        vBBoxMax.y = pCurrent->y;
    //    if (vBBoxMax.z < pCurrent->z)
    //        vBBoxMax.z = pCurrent->z;
    //}

    //float *output = (float*)(&m_mNormalize);
    //output[ 0] = 2.0f/(vBBoxMax[0] - vBBoxMin[0]);
    //output[ 4] = 0.0;
    //output[ 8] = 0.0;
    //output[12] = -(vBBoxMax[0] + vBBoxMin[0]) / (vBBoxMax[0] - vBBoxMin[0]);

    //output[ 1] = 0.0;
    //output[ 5] = 2.0f / (vBBoxMax[1] - vBBoxMin[1]);
    //output[ 9] = 0.0;
    //output[13] = -(vBBoxMax[1] + vBBoxMin[1]) / (vBBoxMax[1] - vBBoxMin[1]);


    //output[ 2] = 0.0;
    //output[ 6] = 0.0;
    //output[10] = 2.0f/(vBBoxMax[2]-vBBoxMin[2]);
    //output[14] = -(vBBoxMax[2] + vBBoxMin[2]) / (vBBoxMax[2] - vBBoxMin[2]);
    //

    //output[ 3] = 0.0;
    //output[ 7] = 0.0;
    //output[11] = 0.0;
    //output[15] = 1.0;
    /*********************/
    // Cleanup
    InFile.close();
    DeleteCache();

    // If an associated material file was found, read that in as well.
    if( strMaterialFilename[0] )
    {
        ( LoadMaterialsFromMTL( strMaterialFilename ) );
    }

    return true;
}


//--------------------------------------------------------------------------------------
mUInt CObjLoader::AddVertex( mUInt hash, TObj_Vertex* pVertex )
{
    // If this vertex doesn't already exist in the Vertices list, create a new entry.
    // Add the index of the vertex to the Indices list.
    mBool bFoundInList = false;
    mUInt index = 0;

    // Since it's very slow to check every element in the vertex list, a hashtable stores
    // vertex indices according to the vertex position's index as reported by the OBJ file
    if( ( mUInt )m_VertexCache.GetSize() > hash )
    {
        TObj_CacheEntry* pEntry = m_VertexCache.GetAt( hash );
        while( pEntry != NULL )
        {
            TObj_Vertex* pCacheVertex = m_Vertices.GetData() + pEntry->index;

            // If this vertex is identical to the vertex already in the list, simply
            // point the index buffer to the existing vertex
            if( 0 == memcmp( pVertex, pCacheVertex, sizeof( TObj_Vertex ) ) )
            {
                bFoundInList = true;
                index = pEntry->index;
                break;
            }

            pEntry = pEntry->pNext;
        }
    }

    // Vertex was not found in the list. Create a new entry, both within the Vertices list
    // and also within the hashtable cache
    if( !bFoundInList )
    {
        // Add to the Vertices list
        index = m_Vertices.GetSize();
        m_Vertices.Add( *pVertex );

        // Add this to the hashtable
        TObj_CacheEntry* pNewEntry = new TObj_CacheEntry;
        if( pNewEntry == NULL )
            return (mUInt)-1;

        pNewEntry->index = index;
        pNewEntry->pNext = NULL;

        // Grow the cache if needed
        while( ( mUInt )m_VertexCache.GetSize() <= hash )
        {
            m_VertexCache.Add( NULL );
        }

        // Add to the end of the linked list
        TObj_CacheEntry* pCurEntry = m_VertexCache.GetAt( hash );
        if( pCurEntry == NULL )
        {
            // This is the head element
            m_VertexCache.SetAt( hash, pNewEntry );
        }
        else
        {
            // Find the tail
            while( pCurEntry->pNext != NULL )
            {
                pCurEntry = pCurEntry->pNext;
            }

            pCurEntry->pNext = pNewEntry;
        }
    }

    return index;
}


//--------------------------------------------------------------------------------------
void CObjLoader::DeleteCache()
{
    // Iterate through all the elements in the cache and subsequent linked lists
    for( mUInt i = 0; i < m_VertexCache.GetSize(); i++ )
    {
        TObj_CacheEntry* pEntry = m_VertexCache.GetAt( i );
        while( pEntry != NULL )
        {
            TObj_CacheEntry* pNext = pEntry->pNext;
            SAFE_DELETE( pEntry );
            pEntry = pNext;
        }
    }

    m_VertexCache.RemoveAll();
}


//--------------------------------------------------------------------------------------
mBool CObjLoader::LoadMaterialsFromMTL( mString strFileName )
{
    // File input
    ifstream InFile( strFileName.c_str() );
    if( !InFile )
    {
        m_Materials.RemoveAll();
        return false;
    }

    char strCommand[50];

    TObj_Material* pMaterial = NULL;

    for(; ; )
    {
        InFile >> strCommand;
        if( !InFile )
            break;

        if( 0 == /*wcscmp*/strcmp( strCommand, "newmtl" ) )
        {
            // Switching active materials
            char strName[256] = {0};
            InFile >> strName;

            pMaterial = NULL;
            for( mUInt i = 0; i < m_Materials.GetSize(); i++ )
            {
                TObj_Material* pCurMaterial = m_Materials.GetAt( i );
                if( 0 == /*wcscmp*/strcmp( pCurMaterial->strName.c_str(), strName ) )
                {
                    pMaterial = pCurMaterial;
                    break;
                }
            }
        }

        // The rest of the commands rely on an active material
        if( pMaterial == NULL )
            continue;

        if( 0 == /*wcscmp*/strcmp( strCommand, "#" ) )
        {
            // Comment
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "Ka" ) )
        {            
            InFile >> pMaterial->vAmbient[0] >> pMaterial->vAmbient[1] >> pMaterial->vAmbient[2];
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "Kd" ) )
        {
            // Diffuse color
            InFile >> pMaterial->vDiffuse[0] >> pMaterial->vDiffuse[1] >> pMaterial->vDiffuse[2];
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "Ks" ) )
        {
            // Specular color
            InFile >> pMaterial->vSpecular[0] >> pMaterial->vSpecular[1] >> pMaterial->vSpecular[2];
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "d" ) ||
                 0 == /*wcscmp*/strcmp( strCommand, "Tr" ) )
        {
            // Alpha
            InFile >> pMaterial->fAlpha;
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "Ns" ) )
        {
            // Shininess
            /*mUInt nShininess;
            InFile >> nShininess;
            pMaterial->nShininess = nShininess;*/
            InFile >> pMaterial->fShininess;
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "illum" ) )
        {
            // Specular on/off
            mUInt illumination;
            InFile >> illumination;
            pMaterial->bSpecular = ( illumination == 2 );
        }
        else if( 0 == /*wcscmp*/strcmp( strCommand, "map_Kd" ) )
        {
            // Texture
            InFile >> pMaterial->strTexture;
        }

        else
        {
            // Unimplemented or unrecognized command
        }

        InFile.ignore( 1000, '\n' );
    }

    InFile.close();

    return true;
}
